//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.UIDesigner.Monitor
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Globalization;
    using System.Linq;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.Commands;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.WpfComposite;
    using Microsoft.Practices.Unity;
    using Microsoft.Research.DataLayer;
    using Microsoft.Research.eResearch.Blackboard.Common;
    using Microsoft.Research.eResearch.Subscriber.Interfaces;

    public partial class DataProductPresentationModel : IDisposable
    {
        /// <summary>
        /// The stack containing the looping activities. 
        /// </summary>
        private Stack<DataProductViewModel> loopingActivitiesStack = new Stack<DataProductViewModel>();

        /// <summary>
        /// The dataproduct service. Retrieves the dataproducts.
        /// </summary>
        private IDataProductService dataProductService;

        /// <summary>
        /// Gets a flag to check the execution is in a looping activity.
        /// </summary>
        private bool InLoopingActivity
        {
            get
            {
                return this.loopingActivitiesStack.Count > 0;
            }
        }

        /// <summary>
        /// Get DataProductViewModel
        /// </summary>
        public ObservableCollection<DataProductViewModel> DataProductsViewCollection { get; private set; }

        /// <summary>
        /// Get DataProductsView
        /// </summary>
        public DataProductsView View { get; private set; }

        /// <summary>
        /// The unity container. Used for dependency injection.
        /// </summary>
        private IUnityContainer container;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="container">IUnityContainer</param>
        /// <param name="view">DataProductsView</param>
        /// <param name="jobCommandProxy">JobCommandProxy</param>
        public DataProductPresentationModel(IUnityContainer container, DataProductsView view, JobCommandProxy jobCommandProxy)
        {
            this.container = container;
            View = view;
            DataProductsViewCollection = new ObservableCollection<DataProductViewModel>();
            loopingActivitiesStack = new Stack<DataProductViewModel>();

            IMonitorService monitorService = container.Resolve<IMonitorService>();
            monitorService.ProcessingStatus += ProcessingStatusRecieved;
            monitorService.DataProductEvent += DataproductCreated;
            monitorService.IterationEvent += IterationStart;
            this.dataProductService = container.Resolve<IDataProductService>();
            this.dataProductService.Clear += delegate { this.DataProductsViewCollection.Clear(); };

            jobCommandProxy.ExecuteCommand.RegisterCommand(new DelegateCommand<object>(p => { this.DataProductsViewCollection.Clear(); DataProductViewModel.AutomationIdSeed = 0; }));
            View.Model = this;
        }

        /// <summary>
        /// Event handlers fires for each activity input output message is recieved
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">BlackboardMessageReceivedEventArgs</param>
        private void ProcessingStatusRecieved(object sender, BlackboardMessageReceivedEventArgs e)
        {
            DateTime executedDateTime = DateTime.MinValue;
            string label;

            if (IsActivityEvent(e.IncomingMessage))
            {
                try
                {
                    executedDateTime = new DateTime(long.Parse(e.IncomingMessage["time:DateTimeDescription"], CultureInfo.InvariantCulture));
                    executedDateTime = TimeZone.CurrentTimeZone.ToLocalTime(executedDateTime);
                }
                catch (FormatException)
                {
                    //Ignore. 
                }
                catch (ArgumentNullException)
                {
                    //Ignore. 
                }
                catch (OverflowException)
                {
                    //Ignore. 
                }

                if (!executedDateTime.Equals(DateTime.MinValue))
                {
                    label = string.Format(CultureInfo.CurrentCulture, "{0} ({1})", e.IncomingMessage["trident:ActivityLabel"], executedDateTime.ToString("MMM-dd-yyyy hh:mm:ss: mmm", CultureInfo.CurrentCulture));
                }
                else
                {
                    label = e.IncomingMessage["trident:ActivityLabel"];
                }

                // If the execution is inside a looping activity then add to the top of the looping stack.
                if (this.InLoopingActivity)
                {
                    DataProductViewModel activityOnTopOfStack = this.loopingActivitiesStack.Peek();

                    // If the message received is older than the current iteration message discard it.
                    if (int.Parse(e.IncomingMessage["trident:EventOrder"], CultureInfo.InvariantCulture) < activityOnTopOfStack.CurrentIteration.EventOrder)
                    {
                        return;
                    }

                    // If closed message then pop out the activity.
                    if (e.IncomingMessage["trident:ActivityState"] == "Closed" &&
                        e.IncomingMessage["trident:ActivityName"] == activityOnTopOfStack.ActivityName)
                    {
                        this.loopingActivitiesStack.Pop();
                        return;
                    }

                    // If a activity message is for the looping activity itself retrive the activity from the stack. 
                    // This is for faulting messages. Post the faulting messages the closing message arrives.
                    if (e.IncomingMessage["trident:ActivityName"].Equals(activityOnTopOfStack.ActivityName))
                    {
                        activityOnTopOfStack.ActivityLabel = label;
                    }
                    else
                    {
                        // Create a entry in the current iteration.
                        DataProductPresentationModel.LookupOrCreate(label, e.IncomingMessage["trident:ActivityName"], activityOnTopOfStack.CurrentIteration.DataProductViewModels);
                    }
                }
                else
                {
                    DataProductPresentationModel.LookupOrCreate(label, e.IncomingMessage["trident:ActivityName"], this.DataProductsViewCollection);
                }
            }
        }

        /// <summary>
        /// Event handlers fired for each dataproduct created message recieved
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">BlackboardMessageReceivedEventArgs</param>
        private void DataproductCreated(object sender, BlackboardMessageReceivedEventArgs e)
        {
            string dataProductId = e.IncomingMessage["trident:DataProductID"];
            DataProductViewModel dataProductViewModel;

            if (!this.InLoopingActivity)
            {
                dataProductViewModel = DataProductPresentationModel.LookupOrCreate(string.Empty,
                    e.IncomingMessage["trident:ActivityName"],
                    this.DataProductsViewCollection);
            }
            else
            {
                //  If in looping activity then add the dataproduct to the activity at the top of the stack.
                dataProductViewModel = DataProductPresentationModel.LookupOrCreate(string.Empty,
                    e.IncomingMessage["trident:ActivityName"],
                    this.loopingActivitiesStack.Peek().CurrentIteration.DataProductViewModels);
            }
            this.dataProductService.GetDataProductInfo(new Guid(dataProductId), dataProductViewModel.DataProducts);
        }

        /// <summary>
        /// Event handlers fired for each iteration message recieved
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">BlackboardMessageReceivedEventArgs</param>
        private void IterationStart(object sender, BlackboardMessageReceivedEventArgs e)
        {
            // If the incoming message is a iteration value add the activity to the looping stack.
            DataProductViewModel requiredDataproductModel = null;
            string activityName = e.IncomingMessage["trident:ActivityName"];
            string activityLabel = e.IncomingMessage["trident:ActivityLabel"];
            if (!this.InLoopingActivity)
            {
                requiredDataproductModel = DataProductPresentationModel.LookupOrCreate(activityLabel, activityName, this.DataProductsViewCollection);
            }
            else
            {
                DataProductViewModel activityOnTopOfStack = this.loopingActivitiesStack.Peek();

                // If the event received is an iteration of the activity at the top of the stack.
                if (activityOnTopOfStack.ActivityName.Equals(activityName))
                {
                    requiredDataproductModel = activityOnTopOfStack;
                }
                // If the event received is an iteration of the activity which is within another looping activity.
                else
                {
                    requiredDataproductModel = DataProductPresentationModel.LookupOrCreate(activityLabel, activityName, activityOnTopOfStack.CurrentIteration.DataProductViewModels);
                }
            }

            // Add the iterations to the required Dataproduct view model.
            DataProductIterationViewModel newIteration = new DataProductIterationViewModel();
            newIteration.Name = "Iteration: " + (requiredDataproductModel.Iterations.Count + 1).ToString(CultureInfo.InvariantCulture) +
                ", Value: " + e.IncomingMessage["trident:IterationValue"];

            newIteration.EventOrder = int.Parse(e.IncomingMessage["trident:EventOrder"], CultureInfo.InvariantCulture);
            requiredDataproductModel.Iterations.Add(newIteration);

            // Push the model to the top of the stack.
            if (loopingActivitiesStack.Count == 0 || requiredDataproductModel != this.loopingActivitiesStack.Peek())
            {
                this.loopingActivitiesStack.Push(requiredDataproductModel);
            }
        }

        /// <summary>
        /// Search for the DataProductViewModel based on the activity name 
        /// Returns the same DataProductViewModel if it finds otherwise it will create a  new DataProductViewModel object 
        /// </summary>
        /// <param name="activityLabel">string</param>
        /// <param name="activityName">string</param>
        /// <returns>The dataproduct model.</returns>
        private static DataProductViewModel LookupOrCreate(string activityLabel, string activityName, ObservableCollection<DataProductViewModel> source)
        {
            // Search the DataProductViewModel in the  DataProductsViewCollection based on its Name.
            DataProductViewModel retrivedDataModel = source.FirstOrDefault(value => activityName.Equals(value.ActivityName));
            if (retrivedDataModel != null)
            {
                if (!string.IsNullOrEmpty(activityLabel))
                {
                    retrivedDataModel.ActivityLabel = activityLabel;
                }
                return retrivedDataModel;
            }
            else
            {
                retrivedDataModel = new DataProductViewModel { ActivityLabel = activityLabel, ActivityName = activityName };
                source.Add(retrivedDataModel);
                return retrivedDataModel;
            }
        }

        /// <summary>
        /// Verify the event message  
        /// </summary>
        /// <param name="message">true if its activity event false otherwise</param>
        /// <returns></returns>
        private static bool IsActivityEvent(BlackboardMessage message)
        {
            return message.ContainsKey("trident:ActivityState");
        }

        /// <summary>
        /// Refreshes the data product Item 
        /// </summary>
        /// <param name="dataProduct">dataproduct from which the info has to be retrieved.</param>
        /// <returns>The dataproduct item.</returns>
        public static DataProductItem GetDataProductItem(DataProduct dataProduct)
        {
            DataProductItem dataProductItem = null;
            try
            {
                dataProductItem = DataProductService.GenerateDataProductItem(dataProduct);
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleUIException(ex);
            }

            return dataProductItem;
        }

        #region IDisposable Members

        /// <summary>
        /// Clears the collection and View Object
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// nullify and Disposes the object 
        /// </summary>
        /// <param name="disposing"></param>
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                this.DataProductsViewCollection.Clear();
                this.DataProductsViewCollection = null;
                this.loopingActivitiesStack.Clear();
                this.View = null;
            }
        }

        #endregion
    }
}